+Mon Mar 1 10:41:27 2004 Owen Taylor <otaylor@redhat.com>
+
+ Patch from Dov Grobgeld to implement auto-bidi-direction
+ for GtkTextView (#118543)
+
+ * gtk/gtktextbtree.[ch]: Resolve bidi base direction
+ for each line by propagating backwards/forwards as
+ necessary.
+
+ * gtk/gtktextlayout.[ch] gtk/gtktextview.c: Set the
+ bidi base direction for the ;ine with the cursor from
+ the keyboard direction. Add gtk_text_layout_set_keyboard_direction().
+
Mon Mar 1 10:31:11 2004 Owen Taylor <otaylor@redhat.com>
- * gtk/gtkentry.c: Implement auto-bidi-direction,
+ * gtk/gtkentry.[ch]: Implement auto-bidi-direction,
based on a patch from Dov Grobgeld. (#118540)
Sun Feb 29 22:01:49 2004 Owen Taylor <otaylor@redhat.com>
+Mon Mar 1 10:41:27 2004 Owen Taylor <otaylor@redhat.com>
+
+ Patch from Dov Grobgeld to implement auto-bidi-direction
+ for GtkTextView (#118543)
+
+ * gtk/gtktextbtree.[ch]: Resolve bidi base direction
+ for each line by propagating backwards/forwards as
+ necessary.
+
+ * gtk/gtktextlayout.[ch] gtk/gtktextview.c: Set the
+ bidi base direction for the ;ine with the cursor from
+ the keyboard direction. Add gtk_text_layout_set_keyboard_direction().
+
Mon Mar 1 10:31:11 2004 Owen Taylor <otaylor@redhat.com>
- * gtk/gtkentry.c: Implement auto-bidi-direction,
+ * gtk/gtkentry.[ch]: Implement auto-bidi-direction,
based on a patch from Dov Grobgeld. (#118540)
Sun Feb 29 22:01:49 2004 Owen Taylor <otaylor@redhat.com>
+Mon Mar 1 10:41:27 2004 Owen Taylor <otaylor@redhat.com>
+
+ Patch from Dov Grobgeld to implement auto-bidi-direction
+ for GtkTextView (#118543)
+
+ * gtk/gtktextbtree.[ch]: Resolve bidi base direction
+ for each line by propagating backwards/forwards as
+ necessary.
+
+ * gtk/gtktextlayout.[ch] gtk/gtktextview.c: Set the
+ bidi base direction for the ;ine with the cursor from
+ the keyboard direction. Add gtk_text_layout_set_keyboard_direction().
+
Mon Mar 1 10:31:11 2004 Owen Taylor <otaylor@redhat.com>
- * gtk/gtkentry.c: Implement auto-bidi-direction,
+ * gtk/gtkentry.[ch]: Implement auto-bidi-direction,
based on a patch from Dov Grobgeld. (#118540)
Sun Feb 29 22:01:49 2004 Owen Taylor <otaylor@redhat.com>
+Mon Mar 1 10:41:27 2004 Owen Taylor <otaylor@redhat.com>
+
+ Patch from Dov Grobgeld to implement auto-bidi-direction
+ for GtkTextView (#118543)
+
+ * gtk/gtktextbtree.[ch]: Resolve bidi base direction
+ for each line by propagating backwards/forwards as
+ necessary.
+
+ * gtk/gtktextlayout.[ch] gtk/gtktextview.c: Set the
+ bidi base direction for the ;ine with the cursor from
+ the keyboard direction. Add gtk_text_layout_set_keyboard_direction().
+
Mon Mar 1 10:31:11 2004 Owen Taylor <otaylor@redhat.com>
- * gtk/gtkentry.c: Implement auto-bidi-direction,
+ * gtk/gtkentry.[ch]: Implement auto-bidi-direction,
based on a patch from Dov Grobgeld. (#118540)
Sun Feb 29 22:01:49 2004 Owen Taylor <otaylor@redhat.com>
+Mon Mar 1 10:41:27 2004 Owen Taylor <otaylor@redhat.com>
+
+ Patch from Dov Grobgeld to implement auto-bidi-direction
+ for GtkTextView (#118543)
+
+ * gtk/gtktextbtree.[ch]: Resolve bidi base direction
+ for each line by propagating backwards/forwards as
+ necessary.
+
+ * gtk/gtktextlayout.[ch] gtk/gtktextview.c: Set the
+ bidi base direction for the ;ine with the cursor from
+ the keyboard direction. Add gtk_text_layout_set_keyboard_direction().
+
Mon Mar 1 10:31:11 2004 Owen Taylor <otaylor@redhat.com>
- * gtk/gtkentry.c: Implement auto-bidi-direction,
+ * gtk/gtkentry.[ch]: Implement auto-bidi-direction,
based on a patch from Dov Grobgeld. (#118540)
Sun Feb 29 22:01:49 2004 Owen Taylor <otaylor@redhat.com>
guint select_words : 1;
guint select_lines : 1;
+ guint resolved_dir : 4; /* PangoDirection */
guint button;
guint blink_timeout;
guint recompute_idle;
* Indexable segment mutation
*/
+/*
+ * The following function is responsible for resolving the bidi direction
+ * for the lines between start and end. But it also calculates any
+ * dependent bidi direction for surrounding lines that change as a result
+ * of the bidi direction decisions within the range. The function is
+ * trying to do as little propagation as is needed.
+ */
+static void
+gtk_text_btree_resolve_bidi (GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GtkTextBTree *tree = _gtk_text_iter_get_btree (start);
+ GtkTextLine *start_line, *end_line, *start_line_prev, *end_line_next, *line;
+ PangoDirection last_strong, dir_above_propagated, dir_below_propagated;
+
+ /* Resolve the strong bidi direction for all lines between
+ * start and end.
+ */
+ start_line = _gtk_text_iter_get_text_line (start);
+ start_line_prev = _gtk_text_line_previous (start_line);
+ end_line = _gtk_text_iter_get_text_line (end);
+ end_line_next = _gtk_text_line_next (end_line);
+
+ line = start_line;
+ while (line && line != end_line_next)
+ {
+ /* Loop through the segments and search for a strong character
+ */
+ GtkTextLineSegment *seg = line->segments;
+ line->dir_strong = PANGO_DIRECTION_NEUTRAL;
+
+ while (seg)
+ {
+ if (seg->byte_count > 0)
+ {
+ PangoDirection pango_dir;
+
+ pango_dir = pango_find_base_dir (seg->body.chars,
+ seg->byte_count);
+
+ if (pango_dir != PANGO_DIRECTION_NEUTRAL)
+ {
+ line->dir_strong = pango_dir;
+ break;
+ }
+ }
+ seg = seg->next;
+ }
+
+ line = _gtk_text_line_next (line);
+ }
+
+ /* Sweep forward */
+
+ /* The variable dir_above_propagated contains the forward propagated
+ * direction before start. It is neutral if start is in the beginning
+ * of the buffer.
+ */
+ dir_above_propagated = PANGO_DIRECTION_NEUTRAL;
+ if (start_line_prev)
+ dir_above_propagated = start_line_prev->dir_propagated_forward;
+
+ /* Loop forward and propagate the direction of each paragraph
+ * to all neutral lines.
+ */
+ line = start_line;
+ last_strong = dir_above_propagated;
+ while (line != end_line_next)
+ {
+ if (line->dir_strong != PANGO_DIRECTION_NEUTRAL)
+ last_strong = line->dir_strong;
+
+ line->dir_propagated_forward = last_strong;
+
+ line = _gtk_text_line_next (line);
+ }
+
+ /* Continue propagating as long as the previous resolved forward
+ * is different from last_strong.
+ */
+ {
+ GtkTextIter end_propagate;
+
+ while (line &&
+ line->dir_strong == PANGO_DIRECTION_NEUTRAL &&
+ line->dir_propagated_forward != last_strong)
+ {
+ GtkTextLine *prev = line;
+ line->dir_propagated_forward = last_strong;
+
+ line = _gtk_text_line_next(line);
+ if (!line)
+ {
+ line = prev;
+ break;
+ }
+ }
+
+ /* The last line to invalidate is the last line before the
+ * line with the strong character. Or in case of the end of the
+ * buffer, the last line of the buffer. (There seems to be an
+ * extra "virtual" last line in the buffer that must not be used
+ * calling _gtk_text_btree_get_iter_at_line (causes crash). Thus the
+ * _gtk_text_line_previous is ok in that case as well.)
+ */
+ line = _gtk_text_line_previous (line);
+ _gtk_text_btree_get_iter_at_line (tree, &end_propagate, line, 0);
+ _gtk_text_btree_invalidate_region (tree, end, &end_propagate);
+ }
+
+ /* Sweep backward */
+
+ /* The variable dir_below_propagated contains the backward propagated
+ * direction after end. It is neutral if end is at the end of
+ * the buffer.
+ */
+ dir_below_propagated = PANGO_DIRECTION_NEUTRAL;
+ if (end_line_next)
+ dir_below_propagated = end_line_next->dir_propagated_back;
+
+ /* Loop backward and propagate the direction of each paragraph
+ * to all neutral lines.
+ */
+ line = end_line;
+ last_strong = dir_below_propagated;
+ while (line != start_line_prev)
+ {
+ if (line->dir_strong != PANGO_DIRECTION_NEUTRAL)
+ last_strong = line->dir_strong;
+
+ line->dir_propagated_back = last_strong;
+
+ line = _gtk_text_line_previous (line);
+ }
+
+ /* Continue propagating as long as the resolved backward dir
+ * is different from last_strong.
+ */
+ {
+ GtkTextIter start_propagate;
+
+ while (line &&
+ line->dir_strong == PANGO_DIRECTION_NEUTRAL &&
+ line->dir_propagated_back != last_strong)
+ {
+ GtkTextLine *prev = line;
+ line->dir_propagated_back = last_strong;
+
+ line = _gtk_text_line_previous (line);
+ if (!line)
+ {
+ line = prev;
+ break;
+ }
+ }
+
+ /* We only need to invalidate for backwards propagation if the
+ * line we ended up on didn't get a direction from forwards
+ * propagation.
+ */
+ if (line && line->dir_propagated_forward == PANGO_DIRECTION_NEUTRAL)
+ {
+ _gtk_text_btree_get_iter_at_line (tree, &start_propagate, line, 0);
+ _gtk_text_btree_invalidate_region(tree, &start_propagate, start);
+ }
+ }
+}
+
void
_gtk_text_btree_delete (GtkTextIter *start,
GtkTextIter *end)
/* Re-initialize our iterators */
_gtk_text_btree_get_iter_at_line (tree, start, start_line, start_byte_offset);
*end = *start;
+
+ _gtk_text_btree_resolve_bidi (start, end);
}
void
/* Convenience for the user */
*iter = end;
+
+ _gtk_text_btree_resolve_bidi (&start, &end);
}
}
GtkTextLine *line;
line = g_new0(GtkTextLine, 1);
+ line->dir_strong = PANGO_DIRECTION_NEUTRAL;
+ line->dir_propagated_forward = PANGO_DIRECTION_NEUTRAL;
+ line->dir_propagated_back = PANGO_DIRECTION_NEUTRAL;
return line;
}
GtkTextLineSegment *segments; /* First in ordered list of segments
* that make up the line. */
GtkTextLineData *views; /* data stored here by views */
+ guchar dir_strong; /* BiDi algo dir of line */
+ guchar dir_propagated_back; /* BiDi algo dir of next line */
+ guchar dir_propagated_forward; /* BiDi algo dir of prev line */
};
#include <stdlib.h>
#include <string.h>
+#define GTK_TEXT_LAYOUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_TEXT_LAYOUT, GtkTextLayoutPrivate))
+
+typedef struct _GtkTextLayoutPrivate GtkTextLayoutPrivate;
+
+struct _GtkTextLayoutPrivate
+{
+ /* Cache the line that the cursor is positioned on, as the keyboard
+ direction only influences the direction of the cursor line.
+ */
+ GtkTextLine *cursor_line;
+};
+
static GtkTextLineData *gtk_text_layout_real_wrap (GtkTextLayout *layout,
GtkTextLine *line,
/* may be NULL */
static PangoAttribute *gtk_text_attr_appearance_new (const GtkTextAppearance *appearance);
+static void gtk_text_layout_mark_set_handler (GtkTextBuffer *buffer,
+ const GtkTextIter *location,
+ GtkTextMark *mark,
+ gpointer data);
+static void gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer,
+ GtkTextIter *iter,
+ gchar *str,
+ gint len,
+ gpointer data);
+static void gtk_text_layout_buffer_delete_range (GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer data);
+
+static void gtk_text_layout_update_cursor_line (GtkTextLayout *layout);
+
enum {
INVALIDATED,
CHANGED,
GTK_TYPE_OBJECT,
G_TYPE_INT,
G_TYPE_INT);
+
+ g_type_class_add_private (object_class, sizeof (GtkTextLayoutPrivate));
}
static void
_gtk_text_btree_remove_view (_gtk_text_buffer_get_btree (layout->buffer),
layout);
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_mark_set_handler),
+ layout);
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_buffer_insert_text),
+ layout);
+ g_signal_handlers_disconnect_by_func (layout->buffer,
+ G_CALLBACK (gtk_text_layout_buffer_delete_range),
+ layout);
+
g_object_unref (layout->buffer);
layout->buffer = NULL;
}
g_object_ref (buffer);
_gtk_text_btree_add_view (_gtk_text_buffer_get_btree (buffer), layout);
+
+ /* Bind to all signals that move the insert mark. */
+ g_signal_connect_after (layout->buffer, "mark_set",
+ G_CALLBACK (gtk_text_layout_mark_set_handler), layout);
+ g_signal_connect_after (layout->buffer, "insert_text",
+ G_CALLBACK (gtk_text_layout_buffer_insert_text), layout);
+ g_signal_connect_after (layout->buffer, "delete_range",
+ G_CALLBACK (gtk_text_layout_buffer_delete_range), layout);
}
}
}
}
+/**
+ * gtk_text_layout_set_keyboard_direction:
+ * @keyboard_dir: the current direction of the keyboard.
+ *
+ * Sets the keyboard direction; this is used as for the bidirectional
+ * base direction for the line with the cursor if the line contains
+ * only neutral characters.
+ **/
+void
+gtk_text_layout_set_keyboard_direction (GtkTextLayout *layout,
+ GtkTextDirection keyboard_dir)
+{
+ if (keyboard_dir != layout->keyboard_direction)
+ {
+ layout->keyboard_direction = keyboard_dir;
+ gtk_text_layout_invalidate_cursor_line (layout);
+ }
+}
+
/**
* gtk_text_layout_get_buffer:
* @layout: a #GtkTextLayout
static void
gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout)
{
- GtkTextIter iter;
- GtkTextLine *line;
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
GtkTextLineData *line_data;
- gtk_text_buffer_get_iter_at_mark (layout->buffer, &iter,
- gtk_text_buffer_get_mark (layout->buffer, "insert"));
+ if (priv->cursor_line == NULL)
+ return;
- line = _gtk_text_iter_get_text_line (&iter);
- line_data = _gtk_text_line_get_data (line, layout);
+ line_data = _gtk_text_line_get_data (priv->cursor_line, layout);
if (line_data)
{
- gtk_text_layout_invalidate_cache (layout, line);
- _gtk_text_line_invalidate_wrap (line, line_data);
+ gtk_text_layout_invalidate_cache (layout, priv->cursor_line);
+ _gtk_text_line_invalidate_wrap (priv->cursor_line, line_data);
gtk_text_layout_invalidated (layout);
}
}
+static void
+gtk_text_layout_update_cursor_line(GtkTextLayout *layout)
+{
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_iter_at_mark (layout->buffer, &iter,
+ gtk_text_buffer_get_mark (layout->buffer, "insert"));
+
+ priv->cursor_line = _gtk_text_iter_get_text_line (&iter);
+}
+
static void
gtk_text_layout_real_invalidate (GtkTextLayout *layout,
const GtkTextIter *start,
static void
set_para_values (GtkTextLayout *layout,
- GtkTextAttributes *style,
+ PangoDirection base_dir,
+ GtkTextAttributes *style,
GtkTextLineDisplay *display)
{
PangoAlignment pango_align = PANGO_ALIGN_LEFT;
int layout_width;
- display->direction = style->direction;
+ switch (base_dir)
+ {
+ /* If no base direction was found, then use the style direction */
+ case PANGO_DIRECTION_NEUTRAL :
+ display->direction = style->direction;
- if (display->direction == GTK_TEXT_DIR_LTR)
- display->layout = pango_layout_new (layout->ltr_context);
- else
+ /* Override the base direction */
+ if (display->direction == GTK_TEXT_DIR_RTL)
+ base_dir = PANGO_DIRECTION_RTL;
+ else
+ base_dir = PANGO_DIRECTION_LTR;
+
+ break;
+ case PANGO_DIRECTION_RTL :
+ display->direction = GTK_TEXT_DIR_RTL;
+ break;
+ default:
+ display->direction = GTK_TEXT_DIR_LTR;
+ break;
+ }
+
+ if (display->direction == GTK_TEXT_DIR_RTL)
display->layout = pango_layout_new (layout->rtl_context);
+ else
+ display->layout = pango_layout_new (layout->ltr_context);
switch (style->justification)
{
case GTK_JUSTIFY_LEFT:
- pango_align = (style->direction == GTK_TEXT_DIR_LTR) ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
+ pango_align = (base_dir == PANGO_DIRECTION_LTR) ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
break;
case GTK_JUSTIFY_RIGHT:
- pango_align = (style->direction == GTK_TEXT_DIR_LTR) ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
+ pango_align = (base_dir == PANGO_DIRECTION_LTR) ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
break;
case GTK_JUSTIFY_CENTER:
pango_align = PANGO_ALIGN_CENTER;
GtkTextLine *line,
gboolean size_only)
{
+ GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
GtkTextLineDisplay *display;
GtkTextLineSegment *seg;
GtkTextIter iter;
GSList *cursor_segs = NULL;
GSList *tmp_list1, *tmp_list2;
gboolean saw_widget = FALSE;
+ PangoDirection base_dir;
g_return_val_if_fail (line != NULL, NULL);
if (totally_invisible_line (layout, line, &iter))
return display;
+ /* Find the bidi base direction */
+ base_dir = line->dir_propagated_forward;
+ if (base_dir == PANGO_DIRECTION_NEUTRAL)
+ base_dir = line->dir_propagated_back;
+
+ if (line == priv->cursor_line &&
+ line->dir_strong == PANGO_DIRECTION_NEUTRAL)
+ {
+ base_dir = (layout->keyboard_direction == GTK_TEXT_DIR_LTR) ?
+ PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
+ }
+
/* Allocate space for flat text for buffer
*/
text_allocated = _gtk_text_line_byte_count (line);
*/
if (!para_values_set)
{
- set_para_values (layout, style, display);
+ set_para_values (layout, base_dir, style, display);
para_values_set = TRUE;
}
if (!para_values_set)
{
style = get_style (layout, &iter);
- set_para_values (layout, style, display);
+ set_para_values (layout, base_dir, style, display);
release_style (layout, style);
}
layout->height, layout->screen_width);
#endif
}
+
+/* Catch all situations that move the insertion point.
+ */
+static void
+gtk_text_layout_mark_set_handler (GtkTextBuffer *buffer,
+ const GtkTextIter *location,
+ GtkTextMark *mark,
+ gpointer data)
+{
+ GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
+
+ if (mark == gtk_text_buffer_get_insert (buffer))
+ gtk_text_layout_update_cursor_line (layout);
+}
+
+static void
+gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer,
+ GtkTextIter *iter,
+ gchar *str,
+ gint len,
+ gpointer data)
+{
+ GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
+
+ gtk_text_layout_update_cursor_line (layout);
+}
+
+static void
+gtk_text_layout_buffer_delete_range (GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer data)
+{
+ GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
+
+ gtk_text_layout_update_cursor_line (layout);
+}
*/
guint cursor_direction : 2;
+ /* The keyboard direction is used to default the alignment when
+ there are no strong characters.
+ */
+ guint keyboard_direction : 2;
+
/* The preedit string and attributes, if any */
gchar *preedit_string;
PangoContext *ltr_context,
PangoContext *rtl_context);
void gtk_text_layout_set_cursor_direction (GtkTextLayout *layout,
- GtkTextDirection direction);
+ GtkTextDirection direction);
+void gtk_text_layout_set_keyboard_direction (GtkTextLayout *layout,
+ GtkTextDirection keyboard_dir);
void gtk_text_layout_default_style_changed (GtkTextLayout *layout);
void gtk_text_layout_set_screen_width (GtkTextLayout *layout,
{
if (text_view->layout)
{
- gboolean split_cursor;
- GtkTextDirection new_dir;
GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
-
+ GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (text_view)));
+ GtkTextDirection new_cursor_dir;
+ GtkTextDirection new_keyboard_dir;
+ gboolean split_cursor;
+
g_object_get (settings,
"gtk-split-cursor", &split_cursor,
NULL);
+
+ if (gdk_keymap_get_direction (keymap) == PANGO_DIRECTION_LTR)
+ new_keyboard_dir = GTK_TEXT_DIR_LTR;
+ else
+ new_keyboard_dir = GTK_TEXT_DIR_RTL;
+
if (split_cursor)
- {
- new_dir = GTK_TEXT_DIR_NONE;
- }
+ new_cursor_dir = GTK_TEXT_DIR_NONE;
else
- {
- GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (text_view)));
- new_dir = (gdk_keymap_get_direction (keymap) == PANGO_DIRECTION_LTR) ?
- GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
- }
+ new_cursor_dir = new_keyboard_dir;
- if (text_view->layout->cursor_direction != new_dir)
- gtk_text_layout_set_cursor_direction (text_view->layout, new_dir);
+ gtk_text_layout_set_cursor_direction (text_view->layout, new_cursor_dir);
+ gtk_text_layout_set_keyboard_direction (text_view->layout, new_keyboard_dir);
}
}